/*
 * A TCPSpellingServer runnable, everytime a new thread runs a new of this.
 * Written by YUE CHI
 */
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Iterator;
import java.util.SortedSet;
import java.util.StringTokenizer;

/**
 * A TCPSpellingServer runnable, everytime a new thread runs a new of this.
 * @author YUE CHI
 * @version 1.1
 */
public class TCPSpellingServer implements Runnable
{

  /**
   * The error string.
   */
  public static final String ERROR_INVALID = "INVALID";
  
  /**
   * The terminate String for end the server.
   */
  public static final String TERMINATION_STRING = "BYE";

  /**
   * The tockensize of input from tcp client.
   */
  public static final int THREE = 3;
  
  /**
   * The token of string.
   */
  public static final String TOKEN = " \r\n";
  
  
  /**
   * The current threadnumber.
   */
  private final int my_cur_threadnumber;
  /**
   * The socket for new client.
   */
  private final Socket my_tcpsocket;
  
  /**
   * The new concurrentSpellingserver everytime for input as a new thread.
   */
  private final ConcurrentTCPSPellingServer my_conserver;

  /**
   * Constructor of the TCPSpellingServer. Each thread call a new one.
   * @param the_contcpselling The current tcpspelling
   * @param the_thread  the thread number
   * @param the_socket  the socket to communicate.
   */
  public TCPSpellingServer(final ConcurrentTCPSPellingServer the_contcpselling, 
                           final int the_thread, final Socket the_socket)
  {
    this.my_cur_threadnumber = the_thread;
    this.my_tcpsocket = the_socket;
    this.my_conserver = the_contcpselling;
  }

  /**
   * The runnuble method. Basic the same as before.
   */
  public void run()
  {
    try
    {
      final String prefix = this.my_cur_threadnumber + "-----";
      Log.out(prefix + "Session start");
      //a buffered reader for the input from tcp client
      final BufferedReader br =
          new BufferedReader(new InputStreamReader(this.my_tcpsocket.getInputStream()));
      //a buffered writer for the output send to tcp client
      final PrintWriter clientout =
          new PrintWriter(new BufferedWriter(new OutputStreamWriter(
                           this.my_tcpsocket.getOutputStream())), true);
      boolean finished = false;
      while (!finished)
      {
        final String str = br.readLine();
        final StringTokenizer stok = new StringTokenizer(str, TOKEN, true);
        final StringTokenizer stok1 = new StringTokenizer(str, TOKEN, false);
        //str!=null avoid continuously reading
        if (str == null)
        {
          Log.err(prefix + "Connection closed unexpectedly");
          finished = true;
        }
        //no input or input = Terminate string like BYEBYE
        else if (str.equals(TERMINATION_STRING) || str.length() == 0)
        {
          clientout.println("GOODBYE");
          this.my_tcpsocket.close();
          finished = true;
          Log.out(prefix + "Connection closed normally");
        }
        else if (str.indexOf(' ') == -1)  //no space for input
        {
          clientout.println(ERROR_INVALID);
          this.my_tcpsocket.close();
          finished = true;
          //here is no space for the input and will show malformed qurery
          Log.err(prefix + "Received malformed query without space.");
        }
        else if (stok.countTokens() > THREE) //query like 1 a ab
        {
          clientout.println(ERROR_INVALID);
          this.my_tcpsocket.close();
          finished = true;
          Log.err(prefix + "Query contains more than 1 space");
        }
        else if (stok1.countTokens() == 2) // right input
        {

          // get the query number for tcp server
          int i = 0;
          try
          {
            i = Integer.parseInt(stok.nextToken());
            if (i <= 0)
            {
              //telnet the port and the client sent a negative number
              clientout.println(ERROR_INVALID);
              this.my_tcpsocket.close();
              finished = true;
              Log.err(prefix + "Query with negative number");
            }
          }
          catch (final NumberFormatException e)
          {
            Log.err(prefix + "No query numbers");
            i = -1;
          }          
          
          if (i > 0)
          { 
            String word = stok.nextToken();
            word = stok.nextToken();          
            final Data outputdata = this.my_conserver.getdata(i, word, 
                                                              this.my_cur_threadnumber);
            final SortedSet<String> wordset = outputdata.getSortedSet();
            final Iterator iterator = wordset.iterator();
            final StringBuffer sb = new StringBuffer();
            // transform the suggestion list into a string and
            // send back to the client side
            if (!outputdata.isInlist())   //The word is not in the list
            {
              while (iterator.hasNext())
              {
                sb.append(iterator.next());
                sb.append(' ');
              }
              clientout.println(i + " NO " + sb.toString());
            }
            else if (outputdata.isInlist()) //The word is spelled right
            {
              clientout.println(i + " OK ");
            }
            Log.out(prefix + "Response sent");
          }
          else
          {
            clientout.println(ERROR_INVALID);
            this.my_tcpsocket.close();
            finished = true;
          }
        
        }
        else  //query like " 1 "
        {
          clientout.println(ERROR_INVALID);
          this.my_tcpsocket.close();
          finished = true;
          Log.err(prefix + "Malformed query.");
        }
      }
    }
    catch (final IOException e)
    {
      Log.err(this.my_cur_threadnumber + "-----unhandled exception" + e);
    }
    try
    {
      this.my_tcpsocket.close();
      return;
    }
    catch (final IOException e)
    {
      Log.err(this.my_cur_threadnumber + "-----exception while closing the socket" + e);
    }

  }

}
